#!/usr/bin/python3
# -*- coding: utf-8 -*-

import sys
import os
import threading
import logging
import re
import asyncio
import time
from datetime import datetime


import db
import settings
import download_m3u8
from logger import set_logger


class Utils(object):
    def __init__(self, content_type, table):
        self._content_type = content_type
        self._table = table
        self._export_key = []

    def __repr__(self):
        return "<%s{content_type: %s, table: %s}>" % (self.__class__.__name__, self._content_type, self._table)

    def getitems(self, path='/root'):
        try:
            session = db.create_session()
            items = [item.dobule_to_dict() for item in session.query(
                self._table).filter_by(path=path).order_by(self._table.created_at.desc())]
            items.sort(key=lambda el: el['content_type'] != 'd')
        except BaseException as e:
            logging.error(e)
            items = []
        return {'items': items, 'path': path[1:].split('/')}

    def getitems_(self, page=None, path='/root'):
        page = page if page is not None and isinstance(
            page, dict) else {'index': 1, 'size': 5}
        try:
            session = db.create_session()
            index = int(page.get('index', 1))
            size = int(page.get('size', 5))
            items = [item.dobule_to_dict() for item in session.query(self._table).filter_by(
                path=path).order_by(self._table.created_at.desc()).offset((index - 1) * size).limit(size)]
            total = len([item for item in session.query(
                self._table).filter_by(path=path)])
            total_page = (total - 1) // size + 1
            pre = False if index == 1 else True
            next_ = index < total_page
            page = {
                'index': index,
                'total': total,
                'total_page': total_page,
                'pre': pre, 'next': next_
            }
        except BaseException as e:
            logging.error(e)
            page = {
                'index': 1,
                'total': 0,
                'total_page': 0,
                'pre': False, 'next': False
            }
            items = []
        return {'items': items, 'page': page, 'path': path[1:].split('/')}

    def get_item(self, fullpath):
        try:
            session = db.create_session()
            ppath, pname = fullpath.rsplit('/', 1)
            if len(ppath) <= 0:
                ppath = '/'
            item = session.query(self._table).filter_by(
                path=ppath, name=pname).first()
            if item.content_type == 'd':
                return {'status': False, 'msg': '路径[%s]不是文件' % fullpath}
            else:
                return {'status': True, 'msg': item.dobule_to_dict()}
        except BaseException as e:
            return {'status': False, 'msg': e}

    def save_item(self, data, user_id=None):
        try:
            session = db.create_session()
            ppath, pname = data['path'].rsplit('/', 1)
            if len(ppath) <= 0:
                ppath = '/'
            else:
                self.mkdirs(ppath)
            item = session.query(self._table).filter_by(
                path=ppath, name=pname).first()
            data['path'] = ppath
            if item:
                if item.content_type == 'd':
                    logging.warning('路径[%s/%s]是文件夹' % (ppath, pname))
                else:
                    if 'name' in data.keys() and data['name'] != pname and len([item_ for item_ in session.query(self._table).filter_by(path=ppath, name=data['name'])]) > 0:
                        return {'status': False, 'msg': '路径[%s]存在[%s]文件不能修改' % (ppath, data['name'])}
                    for key, value in data.items():
                        if hasattr(item, key):
                            setattr(item, key, value)
                    item.updated_at = datetime.now()
                    item.updated_date = item.updated_at.strftime('%Y%m%d')
                    item.updated_stamp = item.updated_at.timestamp()
            else:
                item = self._table(content_type=self._content_type)
                for key, value in data.items():
                    if hasattr(item, key):
                        setattr(item, key, value)
                session.add(item)
            session.commit()
            return {'status': True, 'msg': '%s/%s' % (ppath, data['name'] if 'name' in data.keys() else pname)}
        except BaseException as e:
            return {'status': False, 'msg': e}

    def delete(self, paths, user_id=None):
        try:
            if len(paths) > 0:
                session = db.create_session()
                for path in paths:
                    parent, name = path.rsplit('/', 1)
                    if len(parent) <= 0:
                        return {'status': False, 'msg': '根路径[%s]不能删除' % path}
                    item = session.query(self._table).filter_by(
                        path=parent, name=name).first()
                    if item:
                        if item.content_type == 'd':
                            session.query(self._table).filter(self._table.path.like(
                                '%s%%' % path)).delete(synchronize_session=False)
                        session.delete(item)
                    else:
                        return {'status': False, 'msg': '路径[%s]不存在' % path}
                session.commit()
            else:
                return {'status': False, 'msg': '未勾选移动的项'}
        except BaseException as e:
            return {'status': False, 'msg': e}
        return {'status': True, 'msg': '删除%s成功' % paths}

    def item_export(self, paths, user_id=None):
        try:
            session = db.create_session()
            result = []
            if len(paths) > 0:
                for fullpath in paths:
                    ppath, pname = fullpath.rsplit('/', 1)
                    if len(ppath) == 0:
                        ppath = '/'
                    for item in session.query(self._table).filter_by(path=ppath, name=pname):
                        if item.content_type == 'd':
                            for item_ in session.query(self._table).filter(self._table.path.like('%s%%' % fullpath)):
                                kv = {'content_type': self._content_type}
                                for key in self._export_key:
                                    kv[key] = getattr(item_, key)
                                result.append(kv)
                        else:
                            kv = {'content_type': self._content_type}
                            for key in self._export_key:
                                kv[key] = getattr(item, key)
                            result.append(kv)
        except BaseException as e:
            return {'status': False, 'msg': e}
        return {'status': True, 'msg': result}

    def mkdirs(self, fullpath, user_id=None):
        session = db.create_session()
        ppath, pname = fullpath.rsplit('/', 1)
        if len(ppath) > 0:
            itemitem = session.query(self._table).filter_by(
                name=pname, path=ppath).first()
            if itemitem:
                return
            self.mkdirs(ppath)
            itemitem = self._table(status=1, name=pname,
                                   path=ppath, content_type='d')
            session.add(itemitem)
            session.commit()
        else:
            parent, name = ('/', pname)
            itemitem = session.query(self._table).filter_by(
                name=name, path=parent).first()
            if itemitem:
                return

    def item_import(self, data, user_id=None):
        additem = []
        try:
            session = db.create_session()
            paths = []
            fileitem_exists = []
            fileitem_not_exists = []
            logging.warning('data: %s' % data)
            for item in data:
                if not 'name' in item or not 'path' in item or not item['path'] or len(item['path']) <= 0 or not item['name'] or len(item['name']) <= 0:
                    logging.error('[--]文件[%s/%s]格式不正确: %s' %
                                  (item['path'], item['name'], item))
                    return {'status': False, 'msg': '[--]文件[%s/%s]格式不正确: %s' % (item['path'], item['name'], item)}
                item_ = session.query(self._table).filter_by(
                    name=item['name'], path=item['path']).first()
                if item_:
                    logging.warning('[-]文件[%s/%s]已存在' %
                                    (item['path'], item['name']))
                    fileitem_exists.append(item)
                else:
                    fileitem_not_exists.append(item)
                    if item['path'] in paths:
                        continue
                    paths.append(item['path'])
            logging.warning('fileitem_exists: %s' % fileitem_exists)
            logging.warning('fileitem_not_exists: %s' % fileitem_not_exists)
            logging.warning('paths: %s' % paths)
            for path in paths:
                self.mkdirs(path)
            for item in fileitem_not_exists:
                item_ = self._table(content_type=self._content_type)
                for key in self._export_key:
                    setattr(item_, key, item.get(key, None))
                # item_ = self._table(name=item.get('name', None), path=item.get('path', None), summary=item.get('summary', None), content=item.get(
                #     'content', None), user_id=item.get('user_id', None), content_type=self._content_type)
                session.add(item_)
                session.commit()
                itemtmp = session.query(self._table).filter_by(
                    name=item['name'], path=item['path']).first()
                additem.append(itemtmp)
        except BaseException as e:
            return {'status': False, 'msg': e}
        return {'status': True, 'msg': '导入%d数据' % len(additem)}

    def set_item_status(self, fullpath, status, user_id=None):
        try:
            session = db.create_session()
            ppath, pname = fullpath.rsplit('/', 1)
            if len(ppath) <= 0:
                ppath = '/'
            item = session.query(self._table).filter_by(
                path=ppath, name=pname).first()
            if item:
                if item.content_type == 'd':
                    return {'status': False, 'msg': '路径[%s]是文件夹' % fullpath}
                else:
                    item.status = status
                    item.updated_at = datetime.now()
                    item.updated_date = item.updated_at.strftime('%Y%m%d')
                    item.updated_stamp = item.updated_at.timestamp()
                    session.commit()
                    return {'status': True, 'msg': ppath}
            else:
                return {'status': False, 'msg': '路径[%s]不存在' % fullpath}
        except BaseException as e:
            return {'status': False, 'msg': e}

    def makedir(self, parent, name, user_id=None):
        try:
            session = db.create_session()
            ppath, pname = parent.rsplit('/', 1)
            if len(ppath) <= 0:
                ppath = '/'
            if len([item for item in session.query(self._table).filter_by(path=ppath, name=pname, content_type='d')]) <= 0:
                return {'status': False, 'msg': '文件夹(%s)不存在' % parent}
            if len([item for item in session.query(self._table).filter_by(path=parent, name=name, content_type='d')]) > 0:
                return {'status': False, 'msg': '文件夹(%s/%s)已存在' % (parent, name)}
            item = self._table(name=name, path=parent,
                               user_id=user_id, content_type='d')
            session.add(item)
            session.commit()
            return {'status': True, 'msg': '%s/%s' % (parent, name)}
        except BaseException as e:
            return {'status': False, 'msg': e}

    def move(self, paths, newpath, user_id=None):
        try:
            session = db.create_session()
            if len(paths) > 0:
                # 检查目标路径
                ppath, pname = newpath.rsplit('/', 1)
                if len(ppath) <= 0:
                    ppath = '/'
                item = session.query(self._table).filter_by(
                    path=ppath, name=pname).first()
                if item:
                    if item.content_type != 'd':
                        return {'status': False, 'msg': '目标路径[%s]不是文件夹' % newpath}
                else:
                    return {'status': False, 'msg': '目标路径[%s]不存在' % newpath}
                exits = [item.name for item in session.query(self._table).filter(
                    self._table.path == newpath, self._table.name.in_([path.rsplit('/', 1)[1] for path in paths]))]
                if len(exits) > 0:
                    return {'status': False, 'msg': '目标路径[%s]已经存在以下文件%s' % (newpath, exits)}

                for path in paths:
                    ppath, pname = path.rsplit('/', 1)
                    if len(ppath) <= 0:
                        ppath = '/'
                    for item in session.query(self._table).filter_by(path=ppath, name=pname):
                        if item.content_type == 'd':
                            items = session.query(self._table).filter(
                                self._table.path.like('%s%%' % path))
                            if items:
                                for item_ in items:
                                    item_.path = '%s/%s' % (newpath, item.name)
                                    item_.updated_at = datetime.now()
                                    item_.updated_date = item_.updated_at.strftime(
                                        '%Y%m%d')
                                    item_.updated_stamp = item_.updated_at.timestamp()
                        item.path = newpath
                        item.updated_at = datetime.now()
                        item.updated_date = item.updated_at.strftime('%Y%m%d')
                        item.updated_stamp = item.updated_at.timestamp()
                session.commit()
            else:
                return {'status': False, 'msg': '未勾选移动的项'}
        except BaseException as e:
            return {'status': False, 'msg': e}
        return {'status': True, 'msg': newpath}


class Blogutils(Utils):
    def __init__(self, content_type='blog', table=db.Blog):
        Utils.__init__(self, content_type=content_type, table=table)
        self._export_key = ['path', 'name', 'user_id', 'summary', 'content']


class Scriptutils(Utils):
    def __init__(self, content_type='script', table=db.Script):
        Utils.__init__(self, content_type=content_type, table=table)
        self._export_key = ['path', 'name', 'user_id', 'content']

    async def exec(self, contnet):
        try:
            proc = await asyncio.create_subprocess_exec(
                sys.executable, '-c', contnet,
                stdout=asyncio.subprocess.PIPE,
                stderr=asyncio.subprocess.PIPE)
            stdout, stderr = await proc.communicate()
            # result = f'[{contnet!r} exited with {proc.returncode}]\n\n'
            result = f'[exited with {proc.returncode}]\n\n'
            if stdout:
                result += f'[stdout]\n{stdout.decode()}'
            if stderr:
                result += f'[stderr]\n{stderr.decode()}'
            # data = await proc.stdout.read()
            # result = data.decode('utf-8').rstrip()
            await proc.wait()
            return {'status': True, 'msg': result}
        except BaseException as e:
            return {'status': False, 'msg': e}


class Hlsutils(Utils):
    def __init__(self, content_type='hls', table=db.Hlsitem):
        Utils.__init__(self, content_type=content_type, table=table)
        self._export_key = ['path', 'name', 'referer',
                            'referer_referer', 'poster', 'poster_referer']

    def get_hls_path(self):
        return settings.config['hls']['hls_last']

    def get_poster(self):
        return settings.config['hls']['poster']

    def load_poster(self, parent, name, url):
        '''
        子线程下载封面
        :param parent: m3u8文件所在文件夹
        :param name: m3u8文件名
        :param url: 封面下载地址
        '''
        try:
            result = {}
            uri = url[url.index('://')+3:].replace(':', '_')
            path = '%s/%s' % (self.get_poster(), uri.rsplit('/', 1)
                              [0].replace(':', '_'))
            download_m3u8.downloadtask([url], path)
            session = db.create_session()
            hlsitem = session.query(self._table).filter_by(
                name=name, path=parent).first()
            hlsitem.poster = uri

            hlsitem.updated_at = datetime.now()
            hlsitem.updated_date = hlsitem.updated_at.strftime('%Y%m%d')
            hlsitem.updated_stamp = hlsitem.updated_at.timestamp()
            session.commit()
        except BaseException as e:
            logging.error('[-]%s: %s' % (threading.current_thread().name, e))

    def offline(self, item):
        try:
            result = {}
            m3u8_name = download_m3u8.resource_name(item['referer'])
            if item['url'] and item['url'].endswith(m3u8_name):
                uri = item['url']
                path = '%s/%s' % (self.get_hls_path(),
                                  uri.rsplit('/', 1)[0])
            else:
                referer = item['referer_referer']
                uri_dir = referer[referer.index(
                    '://') + 3:].replace(':', '_')
                uri = '%s/%s' % (uri_dir, m3u8_name)
                path = '%s/%s' % (self.get_hls_path(), uri_dir)
            url = item['referer']
            download_m3u8.downloadm3u8(url, path, result, m3u8_name)
            run_key = {}
            download_m3u8.download_ts(result, run_key=run_key)
            duration = 0
            for key in result.keys():
                if isinstance(result[key], dict) and 'duration' in result[key].keys():
                    duration = int(result[key]['duration'])
            size = run_key.get('size', 0)
            logging.warning('[-]duration: %s, size: %s' %
                            (duration, run_key['size']))
            session = db.create_session()
            hlsitem = session.query(self._table).filter_by(
                path=item['path'], name=item['name']).first()
            if hlsitem:
                if run_key.get(0, True):
                    hlsitem.status = 1
                    hlsitem.url = uri
                    hlsitem.duration = duration
                    hlsitem.size = size
                else:
                    logging.warning('download task pause, need to restart')
                    # -1: 暂停
                    hlsitem.status = -1

                hlsitem.updated_at = datetime.now()
                hlsitem.updated_date = hlsitem.updated_at.strftime('%Y%m%d')
                hlsitem.updated_stamp = hlsitem.updated_at.timestamp()
                session.commit()
        except BaseException as e:
            logging.error('ERROR: %s' % e)
            session = db.create_session()
            hlsitem = session.query(self._table).filter_by(
                path=item['path'], name=item['name']).first()
            if hlsitem:
                logging.warning('download task pause, need to restart')
                # -1: 暂停
                hlsitem.status = -1
                hlsitem.updated_at = datetime.now()
                hlsitem.updated_date = hlsitem.updated_at.strftime('%Y%m%d')
                hlsitem.updated_stamp = hlsitem.updated_at.timestamp()
                session.commit()

    async def start(self, paths):
        try:
            skip = []
            success = []
            for path in paths:
                ppath, pname = path.rsplit('/', 1)
                if len(ppath) <= 0:
                    ppath = '/'
                session = db.create_session()
                item = session.query(self._table).filter_by(
                    path=ppath, name=pname).first()
                if item and item.content_type == 'hls' and item.status == -1:
                    # 下载m3u8 #
                    offline_download_t = threading.Thread(
                        target=self.offline, name='offline_download-%s/%s' % (ppath, pname), args=(item.dobule_to_dict(),))
                    offline_download_t.setDaemon(True)
                    logging.warning('start a new thread: %s' %
                                    offline_download_t.name)
                    offline_download_t.start()
                    # 下载封面 #
                    if item.poster_referer and len(item.poster_referer) > 0:
                        load_poster_t = threading.Thread(
                            target=self.load_poster, name='load_poster-%s/%s' % (ppath, pname), args=(ppath, pname, item.poster_referer))
                        load_poster_t.setDaemon(True)
                        logging.warning('start a new thread: %s' %
                                        load_poster_t.name)
                        load_poster_t.start()
                    item.status = 0
                    item.updated_at = datetime.now()
                    item.updated_date = item.updated_at.strftime('%Y%m%d')
                    item.updated_stamp = item.updated_at.timestamp()
                    session.commit()
                else:
                    skip.append(path)
            return {'status': True, 'msg': 'skip: %s' % skip}
        except BaseException as e:
            return {'status': False, 'msg': e}

    def get_file_path(self, path, name):
        flag = False
        result = {'status': False, 'msg': ''}
        for root, dirs, files in os.walk(path):
            for file in files:
                if file == name:
                    result = {'status': True, 'msg': os.path.join(root, file)}
        return result

    async def pause(self, paths):
        try:
            skip = []
            success = []
            for path in paths:
                ppath, pname = path.rsplit('/', 1)
                if len(ppath) <= 0:
                    ppath = '/'
                session = db.create_session()
                item = session.query(self._table).filter_by(
                    path=ppath, name=pname).first()
                if item and item.content_type == 'hls' and item.status == 0:
                    url = item.referer_referer
                    path = '%s/%s' % (self.get_hls_path(),
                                      url[url.index('://')+3:].replace(':', '_'))
                    result = self.get_file_path(path, 'status.tmp')
                    if result['status']:
                        os.remove(result['msg'])
                    else:
                        return {'status': False, 'msg': 'status.tmp not found'}
                    item.status = -1
                    item.updated_at = datetime.now()
                    item.updated_date = item.updated_at.strftime('%Y%m%d')
                    item.updated_stamp = item.updated_at.timestamp()
                    session.commit()
                else:
                    skip.append(path)
            return {'status': True, 'msg': 'skip: %s' % skip}
        except BaseException as e:
            return {'status': False, 'msg': e}

    async def hls_status(self, path):
        try:
            ppath, pname = path.rsplit('/', 1)
            if len(ppath) <= 0:
                ppath = '/'
            session = db.create_session()
            item = session.query(self._table).filter_by(
                path=ppath, name=pname).first()
            if item and item.content_type == 'hls' and item.status == 0:
                url = item.referer_referer
                path = '%s/%s' % (self.get_hls_path(),
                                  url[url.index('://')+3:].replace(':', '_'))
                result = self.get_file_path(path, 'status.tmp')
                if result['status']:
                    buf = ''
                    with open(result['msg'], 'r') as tmp_file:
                        buf += '%s: %s\n' % (result['msg'],
                                             tmp_file.read().strip())
                    return {'status': True, 'msg': {'status': item.status, 'buf': buf}}
                else:
                    return {'status': False, 'msg': 'status.tmp not found'}
            else:
                return {'status': False, 'msg': '[%s]不是下载中的文件' % path}
        except BaseException as e:
            return {'status': False, 'msg': e}


    async def update_poster(self, paths):
        try:
            skip = []
            for path in paths:
                ppath, pname = path.rsplit('/', 1)
                if len(ppath) <= 0:
                    ppath = '/'
                session = db.create_session()
                item = session.query(self._table).filter_by(
                    path=ppath, name=pname).first()
                if item and item.content_type == 'hls' and item.poster_referer and not item.poster:
                    load_poster_t = threading.Thread(target=self.load_poster, name='load_poster-%s/%s' % (ppath, pname), args=(ppath, pname, item.poster_referer))
                    load_poster_t.setDaemon(True)
                    logging.warning('start a new thread: %s' %
                                    load_poster_t.name)
                    load_poster_t.start()
                else:
                    skip.append(path)
            return {'status': True, 'msg': 'skip: %s' % skip}
        except BaseException as e:
            return {'status': False, 'msg': e}


if __name__ == "__main__":
    db.init_db('dev')
    # blog = Utils('blog', db.Blog)
    blog = Blogutils()
    print(blog)
    print(blog.getitems())
